package com.itedge.infrastructure.service.process.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.activiti.engine.HistoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.HistoricTaskInstanceQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.springframework.transaction.annotation.Transactional;

import com.itedge.infrastructure.domain.IProcessEntity;
import com.itedge.infrastructure.service.domain.IProcessEntityService;
import com.itedge.infrastructure.service.process.IProcessService;

/**
 * Service class for interacting with general processes via activiti services.
 * 
 * @author jhe
 *
 * @param <E extends IProcessEntity>
 */
public abstract class AbstractProcessService<E extends IProcessEntity> implements IProcessService<E> {

	/** Constant for info, that task was deleted by process engine. */
    protected static final String PROCESS_DELETE = "Deleted by process engine";
    
    /** Runtime service instance. */
    protected RuntimeService runtimeService;

    /** Task service instance. */
    protected TaskService taskService;

    /** History service instance. */
    protected HistoryService historyService;
    
    private IProcessEntityService<E> processEntityService;
    
    /**
     * Default constructor.
     * 
     * @param runtimeService
     * @param taskService
     * @param historyService
     * @param processEntityService
     */
    protected AbstractProcessService(RuntimeService runtimeService, TaskService taskService,
    		HistoryService historyService, IProcessEntityService<E> processEntityService) {
    	this.runtimeService = runtimeService;
    	this.taskService = taskService;
    	this.historyService = historyService;
    	this.processEntityService = processEntityService;
    }
    
    @Override
    @Transactional
    public E createEntityProcess(E processEntity) {
        ProcessInstance solutionProcess = runtimeService.startProcessInstanceByKey(getProcessKey());
        processEntity.setProcessInstanceId(solutionProcess.getProcessInstanceId());
        processEntityService.persist(processEntity);
        return processEntity;
    }
    
	@Override
	public boolean isProcessForGivenTaskActive(String taskId) {
		Task activeTask = this.getTaskById(taskId);
		long size = runtimeService.createProcessInstanceQuery()
				.processInstanceId(activeTask.getProcessInstanceId()).count();
		return size == 0 ? false : true;
	}  
    
    @Override
    public List<Task> readAllPendingTasksForProcessInstance(String procId) {
        List<Task> tasks = new ArrayList<Task>();
        tasks = taskService.createTaskQuery().processInstanceId(procId)
        	.orderByTaskName().asc().list();
        return tasks;
    }

    @Override
    public List<HistoricTaskInstance> readAllCompletedTasksForProcess(String procId, boolean orderByStart, boolean asc) {
        HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery().finished().processInstanceId(procId);
        if(orderByStart) {
        	query.orderByHistoricActivityInstanceStartTime();
        } else {
        	query.orderByHistoricTaskInstanceEndTime();
        }
        if(asc) {
        	query.asc();
        } else {
        	query.desc();
        }
        return query.list();
    }

    @Override
    public Task getTaskById(String id) {
        return taskService.createTaskQuery().taskId(id).singleResult();
    }
    
    @Override
    public HistoricTaskInstance getHistoryTaskById(String id) {
    	return historyService.createHistoricTaskInstanceQuery().taskId(id).singleResult();
    }    
    
    @Override
    public List<Task> readAllPendingTasks() {
        List<Task> tasks = new ArrayList<Task>();
        tasks = taskService.createTaskQuery().processDefinitionKey(getProcessKey())
        	.orderByTaskCreateTime().asc().list();
        return tasks;
    }
    
    @Override
    public List<Task> readPendingTasksByName(String likeName, Integer maxResults) {
    	List<Task> tasks = new ArrayList<Task>();
    	TaskQuery query = taskService.createTaskQuery().processDefinitionKey(getProcessKey())
    		.taskNameLike(likeName).orderByTaskCreateTime().asc();
    	if (maxResults == null) {
	    	tasks = query.list();
    	} else {
	    	tasks = query.listPage(0, maxResults);    		
    	}
    	return tasks;
    }
    
    @Override
    public List<Task> readPendingTasks(int firstResult, int maxResults) {
    	List<Task> tasks = new ArrayList<Task>();
    	tasks = taskService.createTaskQuery().processDefinitionKey(getProcessKey())
    		.orderByTaskCreateTime().asc().listPage(firstResult, maxResults);
    	return tasks;
    }
    
    @Override
    public long countAllPendingTasks() {
    	long count = taskService.createTaskQuery().processDefinitionKey(getProcessKey()).count();
    	return count;
    }    
    
    @Override
    public void cancelAllPendingTasksByTaskTrigger(String procId, String deletingTaskId) {
    	List<Task> pendingTasks = readAllPendingTasksForProcessInstance(procId);
    	Iterator<Task> it = pendingTasks.iterator();
    	while(it.hasNext()) {
    		Task currentTask = it.next();
    		if (!currentTask.getId().equals(deletingTaskId)) {
    			taskService.setAssignee(currentTask.getId(), PROCESS_DELETE);
    		    /* second argument indicates that task historic instance should
    		       be also deleted along with runtime instance
    		    */
    			taskService.deleteTask(currentTask.getId(), true);
    		}
    	}
    }
    
}
